
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <time.h>
#include <stdlib.h>
#include <sys/time.h>
#include <string.h>

#include "file_write.h"

static int output_fd;
static size_t write_size;

static char *_generate_log_file_name();
static int _file_write_init(void);
static int _file_write(const void *buf, size_t count);
static int _file_write_deinit(void);

static char *_generate_log_file_name()
{
    struct timeval tv;
    struct tm ptm;
    static char file_str[40];
    gettimeofday(&tv, NULL);
    localtime_r(&(tv.tv_sec), &ptm);
    strftime(file_str, sizeof(file_str), "run-%Y%m%d-%H%M%S.log", &ptm);
    return file_str;
}

static int _file_write_init(void)
{
    write_size = 0;
    char *log_file_name = _generate_log_file_name();
    output_fd = open(log_file_name, O_WRONLY | O_CREAT | O_APPEND, 0644);
    if (output_fd < 0) {
        return -1;
    }
    return 0;
}

static int _file_write(const void *buf, size_t count)
{
    if ((write_size + count) > MAX_LOG_FILE_SIZE) {
        _file_write_deinit();
        if (_file_write_init() < 0)
            return -1;
    }

    const char *p = buf;
    size_t remaining = count;
    while (remaining > 0) {
        ssize_t n = write(output_fd, p, remaining);
        if (n < 0) {
            if (errno == EINTR)
                continue;
            return -2;
        }
        p += n;
        remaining -= n;
    }
    write_size += count;
    return 0;
}

static int _file_write_deinit(void)
{
    if (output_fd >= 0) {
        close(output_fd);
    }
    return 0;
}

/* ------------------bufferred write APIs --------------------------------*/


static size_t buf_used;
static struct timespec last_flush;
static char *write_buf;

int file_write_init()
{
    buf_used = 0;
    clock_gettime(CLOCK_MONOTONIC, &last_flush);
    write_buf = malloc(WRITE_BUFFER_SIZE);
    if (!write_buf) {
        return -1;
    }
    if (_file_write_init()) {
        return -2;
    }
    return 0;
}

int file_write(const void *buf, size_t count)
{
    if (!buf) {
        return -1;
    }
    if (!count) {
        return 0;
    }
    if (count > WRITE_BUFFER_SIZE) {
        if (buf_used > 0) {
            if (_file_write(write_buf, buf_used)) {
                return -2;
            }
            buf_used = 0;
        }
        if (_file_write(buf, count)) {
            return -3;
        }
    } else if (buf_used + count > WRITE_BUFFER_SIZE) {
        if (_file_write(write_buf, buf_used)) {
            return -4;
        }
        buf_used = 0;
        memcpy(write_buf, buf, count);
        buf_used = count;
        clock_gettime(CLOCK_MONOTONIC, &last_flush);
    } else {
        memcpy(write_buf + buf_used, buf, count);
        buf_used += count;
    }
    return 0;
}

int file_write_check()
{
    if (buf_used > 0) {
        struct timespec now;
        clock_gettime(CLOCK_MONOTONIC, &now);
        long elapsed_ms = (now.tv_sec - last_flush.tv_sec) * 1000 + (now.tv_nsec - last_flush.tv_nsec) / 1000000;

        if (elapsed_ms >= FLUSH_TIMEOUT_MS) {
            if (_file_write(write_buf, buf_used)) {
                return -1;
            }
            buf_used = 0;
            last_flush = now;
        }
    }
    return 0;
}

int file_write_flush()
{
    if (buf_used > 0) {
        if (_file_write(write_buf, buf_used)) {
            return -1;
        }
    }
    return 0;
}

int file_write_deinit()
{
    if (write_buf) {
        free(write_buf);
    }
    _file_write_deinit();
    return 0;
}
